package org.benetech.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.benetech.collections.ListMap;

/**
 * Utilities for parsing maps. Non static so that generics can be used.
 * @author Reuben Firmin
 *
 * @param <T>
 * @param <U>
 */
public final class MapUtils<T, U> {

	/**
	 * Default constructor.
	 */
	public MapUtils() {
		// empty
	}

    /**
     * Given a map and a value, find keys which are mapped to this value.
     * @param map The map
     * @param value The value
     * @return never null
     */
    public List<T> reverseMap(final Map<T, U> map, final U value) {
    	final List<T> matchingKeys = new ArrayList<T>();
        for (Map.Entry<T, U> mapping : map.entrySet()) {
            if (mapping.getValue().equals(value)) {
                matchingKeys.add(mapping.getKey());
            }
        }
        return matchingKeys;
    }

    /**
     * Given a listmap and a value, find keys which are mapped to this value.
     * @param map The map
     * @param value The value
     * @return never null
     */
    public List<T> reverseMap(final ListMap<T, U> map, final U value) {
    	final List<T> matchingKeys = new ArrayList<T>();
        for (Map.Entry<T, List<U>> mapping : map.entrySet()) {
            for (U valueCandidate : mapping.getValue()) {
                if (valueCandidate.equals(value)) {
                    matchingKeys.add(mapping.getKey());
                }
            }
        }
        return matchingKeys;
    }

    /**
     * Translate string to a Map structure.
     * @param unparsed String containing zero or more key/value pairs. For example,
     * <pre>
     * key1,value1;key2,value2;key3,value3
     * </pre>
     * @param majorDelimiter Character that separates the series of pairs.
     * @param minorDelimiter Character that separates each pair.
     * @return Map with zero or more entries. Only null if input is null.
     */
	public static Map<String, String> parseAndLoadToMap(final String unparsed, final char majorDelimiter,
		final char minorDelimiter)
    {
		if (unparsed == null) {
			return null;
		}
		final String out = StringUtils.strip(unparsed, " " + majorDelimiter + minorDelimiter);
		final Map<String, String> map = new HashMap<String, String>();
		final String[] keyValues = StringUtils.split(out.trim(), majorDelimiter);
		for (final String keyValue : keyValues) {
			final String[] pair = StringUtils.split(keyValue, minorDelimiter);
			map.put(pair[0], pair[1]);
		}
		return map;
	}

	/**
	 * Add all keys and values to the given set.
	 * @param mapToFlatten The map to flatten to a set.
	 * @return Never null
	 */
	public List<T> getAsList(Map<T, T> mapToFlatten) {
		final Set<T> set = new HashSet<T>();
		for (Map.Entry<T, T> entry : mapToFlatten.entrySet()) {
			set.add(entry.getKey());
			set.add(entry.getValue());
		}
		return (List<T>) Arrays.asList(set.toArray());
	}
}
